package com.yugao.fintech.antelope.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yugao.fintech.antelope.base.exception.enums.GlobalErrorEnum;
import com.yugao.fintech.antelope.base.model.constants.TenantCons;
import com.yugao.fintech.antelope.base.model.module.tenant.TenantContextHolder;
import com.yugao.fintech.antelope.base.ram.RamSdkApi;
import com.yugao.fintech.antelope.base.tenant.core.utils.TenantUtils;
import com.yugao.fintech.antelope.system.assembly.RamApiResourceAssembly;
import com.yugao.fintech.antelope.system.manager.RamModuleManager;
import com.yugao.fintech.antelope.system.manager.SysMenuManager;
import com.yugao.fintech.antelope.system.manager.SysRoleMenuRelManager;
import com.yugao.fintech.antelope.system.mapper.RamResourceMapper;
import com.yugao.fintech.antelope.system.model.ResourceSyncMQ;
import com.yugao.fintech.antelope.system.model.entity.RamApiResource;
import com.yugao.fintech.antelope.system.model.entity.RamModule;
import com.yugao.fintech.antelope.system.model.rqrs.ram.*;
import com.yugao.fintech.antelope.system.service.RamResourceService;
import com.yugao.fintech.framework.assistant.utils.CollectionUtils;
import com.yugao.fintech.framework.assistant.utils.UrlUtils;
import com.yugao.fintech.framework.assistant.utils.exception.BizException;
import com.yugao.fintech.framework.lock.manager.ILockManager;
import com.yugao.fintech.framework.lock.model.ILock;
import com.yugao.fintech.framework.mybatisplus.utils.PageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
public class RamResourceServiceImpl extends ServiceImpl<RamResourceMapper, RamApiResource> implements RamResourceService {
    @Autowired
    private ILockManager lockManager;
    @Autowired
    private RamModuleManager ramModuleManager;
    @Autowired
    private RamApiResourceAssembly ramApiResourceAssembly;
    @Autowired
    private SysMenuManager menuDataManager;
    @Autowired
    private RamSdkApi ramSdkApi;
    @Autowired
    private SysRoleMenuRelManager roleMenuRelManager;

    @Resource(name = "myThreadPool")
    private ThreadPoolTaskExecutor myThreadPool;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 分页查询 访问资源
     *
     * @return 返回分页数据
     */
    @Override
    public IPage<RamResourceResp> pageResource(RamResourcePageReq req) {
        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getBeginLocalDateTime();
        LambdaQueryWrapper<RamApiResource> queryWrapper = RamApiResource.lqw()
                .like(StringUtils.isNotEmpty(req.getModuleCode()), RamApiResource::getModuleCode, req.getModuleCode())
                .like(StringUtils.isNotEmpty(req.getAction()), RamApiResource::getAction, req.getAction())
                .eq(StringUtils.isNotEmpty(req.getActionType()), RamApiResource::getActionType, req.getActionType())
                .like(StringUtils.isNotEmpty(req.getName()), RamApiResource::getName, req.getName())
                .ge(Objects.nonNull(beginTime), RamApiResource::getCreatedAt, beginTime)
                .le(Objects.nonNull(endTime), RamApiResource::getCreatedAt, endTime)
                .orderByDesc(RamApiResource::getCreatedAt);
        IPage<RamApiResource> pageData = this.page(PageUtil.to(req), queryWrapper);
        Set<String> moduleCodes = pageData.getRecords().stream().map(RamApiResource::getModuleCode).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(moduleCodes)) {
            return new Page<>();
        }

        Map<String, RamModule> moduleMap = ramModuleManager.list(RamModule.lqw().in(RamModule::getCode, moduleCodes))
                .stream().collect(Collectors.toMap(RamModule::getCode, Function.identity()));

        return PageUtil.to(pageData, (e) -> {
            RamResourceResp rs = ramApiResourceAssembly.toResourceResp(e);
            rs.setModuleName(Optional.ofNullable(moduleMap.get(e.getModuleCode())).map(RamModule::getName).orElse(e.getModuleCode()));
            return rs;
        });
    }

    /**
     * 查询 访问资源 所有记录
     *
     * @return 返回集合，没有返回空List
     */
    @Override
    public List<RamResourceResp> listResource(RamResourceListReq req) {
        LambdaQueryWrapper<RamApiResource> lqw = RamApiResource.lqw()
                .like(StringUtils.isNotEmpty(req.getModuleCode()), RamApiResource::getModuleCode, req.getModuleCode())
                .like(StringUtils.isNotEmpty(req.getAction()), RamApiResource::getAction, req.getAction())
                .eq(StringUtils.isNotEmpty(req.getActionType()), RamApiResource::getActionType, req.getActionType())
                .like(StringUtils.isNotEmpty(req.getName()), RamApiResource::getName, req.getName())
                .orderByDesc(RamApiResource::getAction);
        return this.list(lqw).stream().distinct()
                .map(ramApiResourceAssembly::toResourceResp).collect(Collectors.toList());
    }


    /**
     * 根据主键查询 访问资源
     *
     * @param id 主键
     * @return 返回记录，没有返回null
     */
    @Override
    public RamResourceResp getResourceById(Long id) {
        RamApiResource entity = this.getById(id);
        return ramApiResourceAssembly.toResourceResp(entity);
    }

    /**
     * 保存 访问资源
     *
     * @param req 新增的记录
     * @return 返回影响行数
     */
    @Override
    public boolean saveResource(RamResourceSaveReq req) {
        RamApiResource entity = ramApiResourceAssembly.toResource(req);
        entity.setId(null);
        return this.save(entity);
    }


    /**
     * 更新 访问资源
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateResourceById(RamResourceUpdateReq req) {
        TenantUtils.executeIgnore(TenantCons.Type.ALL, () -> {
            RamApiResource entity = ramApiResourceAssembly.toResource(req);

            RamApiResource record = this.getById(req.getId());
            if (Objects.isNull(record)) {
                return;
            }

            if (!record.getAction().equals(req.getAction())) {
                // 更新菜单表中perms
                menuDataManager.updatePerm(record.getAction(), req.getAction());
            }

            // 更新角色菜单表中的action
            roleMenuRelManager.updateAction(record.getAction(), req.getAction());

            boolean ret = this.updateById(entity);

            if (!ret) {
                throw new BizException(GlobalErrorEnum.OPERATION_FAIL_UPDATE);
            }
        });
    }

    /**
     * 通过id删除 访问资源
     *
     * @param id 删除的主键
     * @return 返回影响行数
     */
    @Override
    public boolean deleteResourceById(Long id) {
        return this.removeById(id);
    }

    /**
     * 通过ids删除 访问资源
     *
     * @param ids 删除的主键
     * @return 返回影响行数
     */
    @Override
    public boolean deleteResourceByIds(List<Long> ids) {
        return this.removeByIds(ids);
    }

    @Override
    public List<RamApiResource> listResource(boolean isAdmin, Set<String> actions) {
        List<RamApiResource> apiResourceList = new ArrayList<>();
        if (isAdmin) {
            RamApiResource resource = new RamApiResource();
            resource.setUrl("/**");
            resource.setAction("*:*:*");
            apiResourceList.add(resource);
            return apiResourceList;
        }

        LambdaQueryWrapper<RamApiResource> lqw = RamApiResource.lqw()
                .in(CollectionUtils.isNotEmpty(actions), RamApiResource::getAction, actions)
                .or(e -> e.eq(RamApiResource::getIsPersonal, true));
        return this.list(lqw)
                .stream()
                .peek(e -> e.setUrl(UrlUtils.removeRepeatSlashOfUrl(e.getUrl())))
                .collect(Collectors.toList());
    }

    @Override
    public void syncResource(SyncResourceReq rq) {
        Long tenantId = TenantContextHolder.get().getTenantId();
        if (TenantCons.SYSTEM_TENANT_ID.compareTo(tenantId) != 0) {
            throw new BizException("该资源只有管理员租户才能操作");
        }
        List<String> saveOrUpdateError = new CopyOnWriteArrayList<>();

        List<String> errorData = ramSdkApi.listSyncResource((serviceId, data) -> {
            log.info("sync serviceId: {}, size: {}", serviceId, data.size());
            ILock lock = null;
            try {

                String lockKey = tenantId + ":syncResource";
                lock = lockManager.getLock(lockKey);
                lock.tryLock(60, TimeUnit.SECONDS);

                List<RamApiResource> newResourceList = data.stream().map(e -> {
                    // 保存或者更新资源
                    return RamApiResource.builder().actionType(e.getActionType())
                            .action(e.getAction()).moduleCode(e.getModuleCode())
                            .name(e.getName()).url(e.getUrl()).isPersonal(e.getIsPersonal())
                            .build();
                }).collect(Collectors.toList());

                Map<String, RamApiResource> oldResourceMap = this.list().stream()
                        .collect(Collectors.toMap(RamApiResource::getUrl, Function.identity()));

                Map<String, RamModule> oldModuleMap = ramModuleManager.list().stream()
                        .collect(Collectors.toMap(RamModule::getCode, Function.identity()));

                List<RamApiResource> saveResourceList = new ArrayList<>();
                Map<String, RamModule> saveModuleMap = new HashMap<>();
                if (oldResourceMap.isEmpty()) {
                    saveResourceList = new ArrayList<>(newResourceList);
                }

                for (RamApiResource newResource : newResourceList) {
                    RamApiResource oldResource = oldResourceMap.get(newResource.getUrl());
                    if (Objects.nonNull(oldResource) && !oldResource.getAction().equals(newResource.getAction())) {
                        String oldAction = oldResource.getAction();
                        String newAction = newResource.getAction();
                        menuDataManager.updatePerm(oldAction, newAction);
                    }

                    // 需要保存或者更新的资源
                    if (Objects.nonNull(oldResource)) {
                        newResource.setId(oldResource.getId());
                        ramApiResourceAssembly.updateResource(newResource, oldResource);
                        saveResourceList.add(oldResource);
                    } else {
                        saveResourceList.add(newResource);
                    }

                    // 需要保存或者更新的资源模块
                    RamModule module = oldModuleMap.get(newResource.getModuleCode());
                    RamModule ramModule;
                    if (Objects.isNull(module)) {
                        ramModule = RamModule.builder()
                                .code(newResource.getModuleCode()).name(newResource.getModuleCode()).build();
                    } else {
                        ramModule = RamModule.builder().id(module.getId()).code(newResource.getModuleCode()).build();
                    }
                    saveModuleMap.put(newResource.getModuleCode(), ramModule);
                }

                this.saveOrUpdateBatch(saveResourceList);
                ramModuleManager.saveOrUpdateBatch(saveModuleMap.values());
            } catch (Exception e) {
                saveOrUpdateError.add(serviceId + " = " + e.getMessage());
                log.error("error: ", e);
            } finally {
                if (Objects.nonNull(lock)) {
                    lock.unlock();
                }
            }
        });

        Set<String> errorSet = new HashSet<>();
        errorSet.addAll(saveOrUpdateError);
        errorSet.addAll(errorData);
        if (CollectionUtils.isNotEmpty(errorSet)) {
            throw new BizException("有服务同步失败: " + errorSet);
        }

        List<RamModule> allModule = ramModuleManager.list();
        rabbitTemplate.convertAndSend(
                ResourceSyncMQ.EXCHANGE, ResourceSyncMQ.KEY_NOTICE_REFRESH, new ResourceSyncMQ(allModule).toMessage()
        );
    }
}
